/****************************************************************************************
*	Mouzetrap - client/server framework	
*
*	$HeadURL: http://mouzetrap.googlecode.com/svn/trunk/Mouzetrap/BufferPool.cs $
*	$Id: BufferPool.cs 11 2009-06-11 09:10:25Z tobz@fallenrealms.net $
* 
*	Copyright (c) 2008 Toby Lawrence
* 
*	Permission is hereby granted, free of charge, to any person obtaining a copy
*	of this software and associated documentation files (the "Software"), to deal
*	in the Software without restriction, including without limitation the rights
*	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
*	copies of the Software, and to permit persons to whom the Software is
*	furnished to do so, subject to the following conditions:
* 
*	The above copyright notice and this permission notice shall be included in
*	all copies or substantial portions of the Software.
* 
*	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
*	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
*	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
*	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
*	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
*	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
*	THE SOFTWARE.
* 
****************************************************************************************/

using System;
using Mouzetrap.DataStructures;
using Mouzetrap.Localization;
using Mouzetrap.Threading;

namespace Mouzetrap
{
	/// <summary>
	/// Manages large amounts of memory, broken into smaller, usable chunks.
	/// </summary>
	/// <remarks>
	/// We work off of the idea objects that are large enough (currently, should be around 80kb) 
	/// are allocated on a special heap, called the Large Object Heap. (LOH)  This heap is collected,
	/// but it is not compacted because of the nature of the GC and the performance implications.
	/// This allows us to allocate a lot of memory, which we know we'll use, and avoid requiring the GC 
	/// to consider it when doing normal collections.  
	/// 
	/// Also from this, we can assert this memory will not move.  With that in mind, the chunks of 
	/// memory can be pointed to with an unsafe pointer, allowing for possible performance optimizations 
	/// to be made, or for the memory to be easily used with unmanaged code.
	/// 
	/// There is no gaurentee, however, that the .NET runtime will always have this behavior nor can we say
	/// that the minimum size for an object to be put on the LOH will stay the same. With this in mind, use
	/// pointers to allocated segments cautiously, pinning manually as needed.
	/// </remarks>
	public sealed class BufferPool : DisposableObject
	{
		private readonly LockfreeStack<byte[]> _allocatedSegments;
		private readonly LockfreeStack<ArraySegment<byte>> _childBuffers;
		private readonly int _chunkSize;
		private int _availableBuffers;
		private SpinWaitLock _changeLock;
		private int _totalChunks;

		internal BufferPool()
		{
		}

		/// <summary>
		/// Creates a new buffer pool, with the given number of initial buffers, at the given buffer size.
		/// </summary>
		/// <param name="bufferCount">the number of initial buffers to create</param>
		/// <param name="chunkSize">the size of the chunks</param>
		internal BufferPool(int bufferCount, int chunkSize)
		{
			if(bufferCount < 1)
				throw new ArgumentOutOfRangeException("bufferCount", Mouzetrap_Messages.BufferCountZero);

			if(chunkSize < 1)
				throw new ArgumentOutOfRangeException("chunkSize", Mouzetrap_Messages.ChunkSizeZero);

			_chunkSize = chunkSize;

			_allocatedSegments = new LockfreeStack<byte[]>();
			_childBuffers = new LockfreeStack<ArraySegment<byte>>();

			int initialNeededSegment = MathHelper.GetNearestPowerOfTwo(bufferCount * chunkSize);
			AllocateSegment(initialNeededSegment);
		}

		/// <summary>
		/// The total number of buffers registered with this pool.
		/// </summary>
		public int TotalBuffers
		{
			get
			{
				if(IsDisposed)
					throw new ObjectDisposedException("buffer pool");

				return _totalChunks;
			}
		}

		/// <summary>
		/// The number of available buffers in the pool.
		/// </summary>
		public int AvailableBuffers
		{
			get
			{
				if(IsDisposed)
					throw new ObjectDisposedException("buffer pool");

				return _availableBuffers;
			}
		}

		/// <summary>
		/// The size of chunks given by this pool.
		/// </summary>
		public int BufferSize
		{
			get
			{
				if(IsDisposed)
					throw new ObjectDisposedException("buffer pool");

				return _chunkSize;
			}
		}

		/// <summary>
		/// Grabs a buffer from the pool.
		/// </summary>
		/// <remarks>
		/// If the pool is empty, more buffers will be created and added to the pool to service the request.
		/// </remarks>
		/// <returns>a buffer pointing to a chunk of a larger memory segment</returns>
		public ArraySegment<byte> GetBuffer()
		{
			if(IsDisposed)
				throw new ObjectDisposedException("buffer pool");

			ArraySegment<byte> buf;

			if(!_childBuffers.Pop(out buf))
			{
				_changeLock.Enter();

				try
				{
					while(!_childBuffers.Pop(out buf))
					{
						IncreaseCapacity();
					}
				}
				finally
				{
					_changeLock.Exit();
				}
			}

			_availableBuffers--;

			return buf;
		}

		/// <summary>
		/// Releases a buffer back to the pool.
		/// </summary>
		/// <param name="buffer">the buffer to release</param>
		public void ReturnBuffer(ArraySegment<byte> buffer)
		{
			if(IsDisposed)
				throw new ObjectDisposedException("buffer pool");

			_childBuffers.Push(buffer);

			_availableBuffers++;
		}

		private void IncreaseCapacity()
		{
			// we only increase capacity when we create the pool, or can't serve a request (starvation)
			// so we use the figure of 5% as how much we'll increase our overall capacity.
			int neededSegmentSize = (((int) (_totalChunks * 0.05)) * _chunkSize);

			// handles low buffer count situations where 'neededSegmentSize' may truncate to 0
			// and will yield atleast 1 child chunk
			if(neededSegmentSize < _chunkSize)
			{
				neededSegmentSize = _chunkSize;
			}

			int roundedSegmentSize = MathHelper.GetNearestPowerOfTwo(neededSegmentSize);

			AllocateSegment(roundedSegmentSize);
		}

		private void AllocateSegment(int segmentSize)
		{
			var newSegment = new byte[segmentSize];

			// create the child buffers
			for(int offset = 0; offset < segmentSize; offset += _chunkSize)
			{
				// for cases when the chunk size isn't a power of two.
				if(offset + _chunkSize > segmentSize)
					break;

				_childBuffers.Push(new ArraySegment<byte>(newSegment, offset, _chunkSize));
			}

			// get the total chunks created here, and add that to our total count and available count
			int newChunks = (segmentSize / _chunkSize);

			_totalChunks += newChunks;
			_availableBuffers += newChunks;

			_allocatedSegments.Push(newSegment);
		}

		protected override void Dispose(bool disposing)
		{
			base.Dispose(disposing);

			if(disposing)
			{
				// clear child buffers
				ArraySegment<byte> buf;
				while(_childBuffers.Pop(out buf)) ;

				// dispose/clear segment chunks
				byte[] segment;
				while(_allocatedSegments.Pop(out segment)) ;
			}
		}
	}
}